import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;

/**
 * Implements the library operations.
 * 
 * @author Gadi Elishayov - 026540005
 * @author Ruby Fienstein - 310211074
 */
public class Library {
	
	public Library() {
		for (int i = 0; i < Constants.MAX_BOOKS_PER_SUBSCRIBER; ++i) {
			counters.add(i, new RealLinkedList());
		}
	}
	
	/**
	 * Adds a subscriber to the hash.
	 * 
	 * Complexity: O(1) in the average case.
	 * 
	 * @param s The subscriber to add.
	 */
	public void addSubscriber(Subscriber s) {
		if (subscribers.containsKey(s.id)) {
			throw new IllegalStateException("A subscriber with that id already exists");
		}
		
		subscribers.put(s.id, s);
	}
	
	/**
	 * Adds a book to the hash.
	 * 
	 * Complexity: O(1) in the average case.
	 * 
	 * @param s The book to add.
	 */
	public void addBook(Book b) {
		if (books.containsKey(b.id)) {
			throw new IllegalStateException("A book with that id already exists");
		}
		
		books.put(b.id, b);
	}
	
	/**
	 * Borrows a book.
	 * 
	 * Complexity: O(1) in the average case.
	 * 
	 * @param lastName     The subscriber last name.
	 * @param subscriberId The subscriber id.
	 * @param bookId       The book id.
	 */
	public void borrowBook(String lastName, String subscriberId, String bookId) {
		if (!books.containsKey(bookId) || !subscribers.containsKey(subscriberId)) {
			throw new IllegalArgumentException();
		}
		
		Subscriber s = subscribers.get(subscriberId);
		if (!s.last_name.equals(lastName)) {
			throw new IllegalArgumentException();
		}
		Book b = books.get(bookId);
		
		// This book is already borrowed
		if (b.owner != null) {
			throw new IllegalStateException("The book is already borrowed");
		}
		
		// The subscriber cannot borrow any more books
		if (s.books.size() == Constants.MAX_BOOKS_PER_SUBSCRIBER) {
			throw new IllegalStateException("Too many books");
		}
		
		// Borrow the book
		b.owner = s;
		if (s.books.size() != 0) {
			decreaseCount(s.books.size(), s);	
		}
		s.books.add(b);
		increateCount(s.books.size(), s);
	}
	
	/**
	 * Return a borrowed book.
	 * 
	 * Complexity: O(1) in the average case.
	 * 
	 * @param lastName     The subscriber last name.
	 * @param subscriberId The subscriber id.
	 * @param bookId       The book id.
	 */
	public void returnBook(String lastName, String subscriberId, String bookId) {
		if (!books.containsKey(bookId) || !subscribers.containsKey(subscriberId)) {
			throw new IllegalArgumentException();
		}
		
		Subscriber s = subscribers.get(subscriberId);
		if (!s.last_name.equals(lastName)) {
			throw new IllegalArgumentException();
		}
		Book b = books.get(bookId);
		
		// This book is not borrowed
		if (b.owner == null) {
			throw new IllegalStateException("The book is not borrowed");
		} else if (b.owner != s) {
			throw new IllegalStateException("The book is borrowed to another subscriber");
		}
		
		// Return the book
		b.owner = null;
		decreaseCount(s.books.size(), s);
		s.books.remove(b);
		increateCount(s.books.size(), s);
	}
	
	/**
	 * Deletes a subscriber
	 * 
	 * Complexity: O(1) in the average case.
	 * 
	 * @param lastName     The subscriber last name.
	 * @param subscriberId The subscriber id.
	 */
	public void delSubscriber(String lastName, String subscriberId) {
		if (!subscribers.containsKey(subscriberId)) {
			throw new IllegalArgumentException();
		}
		
		Subscriber s = subscribers.get(subscriberId);
		if (!s.last_name.equals(lastName)) {
			throw new IllegalArgumentException();
		}
		decreaseCount(s.books.size(), s);
		
		Book b = (Book) s.books.head();
		while (b != null) {
			b.owner = null;
			b = (Book) b.next;
		}
		subscribers.remove(subscriberId);
	}
	
	/**
	 * Complexity: O(1) in the average case.
	 *  
	 * @param subscriberId The subscriber id.
	 * @return the books a subscriber currently borrows.
	 */
	public RealLinkedList getBooks(String subscriberId) {
		if (!subscribers.containsKey(subscriberId)) {
			throw new IllegalArgumentException();
		}
		
		return subscribers.get(subscriberId).books;
	}
	
	/**
	 * Complexity: O(1) in the average case.
	 *  
	 * @param bookId The book id.
	 * @return the subscriber who owns 
	 */
	public Subscriber getBookOwner(String bookId) {
		if (!books.containsKey(bookId)) {
			throw new IllegalArgumentException();
		}
		
		return books.get(bookId).owner;
	}
	
	/**
	 * Complexity: O(1).
	 *  
	 * @return A list of subscriber who owns the most amount of books. 
	 */
	public RealLinkedList getSubscribersWithMostBooks() {
		for (int i = Constants.MAX_BOOKS_PER_SUBSCRIBER - 1; i >= 0; --i) {
			if (counters.get(i).size() > 0) {
				return counters.get(i);
			}
		}
		
		// None found - return an empty list
		return new RealLinkedList();
	}
	
	private void decreaseCount(int count, Subscriber sub) {
		if (count == 0)
			return;
		
		counters.get(count - 1).remove(sub);
	}
	
	private void increateCount(int count, Subscriber sub) {
		if (count == 0)
			return;
		
		counters.get(count - 1).add(sub);
	}
	
	private Hashtable<String, Subscriber> subscribers =
			new Hashtable<String, Subscriber>();
	private Hashtable<String, Book>       books =
			new Hashtable<String, Book>();
	private ArrayList<RealLinkedList> counters =
			new ArrayList<RealLinkedList>(Constants.MAX_BOOKS_PER_SUBSCRIBER);
}
